package com.artisan.spider.service.impl;


import com.alibaba.fastjson.JSON;
import com.artisan.spider.domain.SpScheduled;
import com.artisan.spider.mapper.SpScheduledMapper;
import com.artisan.spider.service.ISpScheduledService;
import com.artisan.spider.service.ScheduledTaskService;
import com.artisan.spider.util.SpiderUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 定时任务实现
 */
@Service
@Slf4j
public class ScheduledTaskServiceImpl implements ScheduledTaskService {


    @Autowired
    private SpScheduledMapper taskMapper;
    /**
     * 可重入锁
     */
    private ReentrantLock lock = new ReentrantLock();
    /**
     * 定时任务线程池
     */
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Autowired
    private ISpScheduledService iSpScheduledService;


    /**
     * 存放已经启动的任务map
     */
    private Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>();

    @Autowired
    SpiderUtil spiderUtil;

    /**
     * 所有任务列表
     */
    @Override
    public List<SpScheduled> taskList() {
        log.info(">>>>>> 获取任务列表开始 >>>>>> ");
        //数据库查询所有任务 => 未做分页
        List<SpScheduled> taskBeanList = taskMapper.getAllTask();
        if (CollectionUtils.isEmpty(taskBeanList)) {
            return new ArrayList<>();
        }

        for (SpScheduled taskBean : taskBeanList) {
            String taskKey = taskBean.getTaskKey();
            //是否启动标记处理
            taskBean.setStartFlag(this.isStart(taskKey)?1:0);
        }
        log.info(">>>>>> 获取任务列表结束 >>>>>> ");
        return taskBeanList;
    }


    /**
     * 根据任务key 启动任务
     */
    @Override
    public Boolean start(String taskKey) {
        log.info(">>>>>> 启动任务 {} 开始 >>>>>>", taskKey);
        //添加锁放一个线程启动，防止多人启动多次
        lock.lock();
        log.info(">>>>>> 添加任务启动锁完毕");
        try {
            //校验是否已经启动
            if (this.isStart(taskKey)) {
                log.info(">>>>>> 当前任务已经启动，无需重复启动！");
                return false;
            }

            //根据key数据库获取任务配置信息
            SpScheduled scheduledTask = taskMapper.getByKey(taskKey);
            //启动任务
            this.doStartTask(scheduledTask);
        } finally {
            // 释放锁
            lock.unlock();
            log.info(">>>>>> 释放任务启动锁完毕");
        }
        log.info(">>>>>> 启动任务 {} 结束 >>>>>>", taskKey);
        return true;
    }

    /**
     * 根据 key 停止任务
     */
    @Override
    public Boolean stop(String taskKey) {
        log.info(">>>>>> 进入停止任务 {}  >>>>>>", taskKey);
        //当前任务实例是否存在
        boolean taskStartFlag = scheduledFutureMap.containsKey(taskKey);
        log.info(">>>>>> 当前任务实例是否存在 {}", taskStartFlag);
        if (taskStartFlag) {
            //获取任务实例
            ScheduledFuture scheduledFuture = scheduledFutureMap.get(taskKey);
            //关闭实例
            scheduledFuture.cancel(true);
        }
        log.info(">>>>>> 结束停止任务 {}  >>>>>>", taskKey);
        return taskStartFlag;
    }

    /**
     * 根据任务key 重启任务
     */
    @Override
    public Boolean restart(String taskKey) {
        log.info(">>>>>> 进入重启任务 {}  >>>>>>", taskKey);
        //先停止
        this.stop(taskKey);
        //再启动
        return this.start(taskKey);
    }

    /**
     * 程序启动时初始化  ==> 启动所有正常状态的任务
     */
    @Override
    public void initAllTask(List<SpScheduled> scheduledTaskBeanList) {
        log.info("程序启动 ==> 初始化所有任务开始 ！size={}", scheduledTaskBeanList.size());
        if (CollectionUtils.isEmpty(scheduledTaskBeanList)) {
            return;
        }
        for (SpScheduled scheduledTask : scheduledTaskBeanList) {
            //任务 key
            String taskKey = scheduledTask.getTaskKey();
            //校验是否已经启动
            if (this.isStart(taskKey)) {
                continue;
            }
            //启动任务
            this.doStartTask(scheduledTask);
        }
        log.info("程序启动 ==> 初始化所有任务结束 ！size={}", scheduledTaskBeanList.size());
    }

    /**
     * 执行启动任务
     */
    private void doStartTask(SpScheduled scheduledTask) {
        //任务key
        String taskKey = scheduledTask.getTaskKey();
        //定时表达式
        String taskCronOriginal = scheduledTask.getTaskCron();
        log.info(">>>>>> 任务 [ {} ] ,原来的cron={}", scheduledTask.getTaskDesc(), taskCronOriginal);
        ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(() -> {

                    collectionInfo(scheduledTask);

        },
                triggerContext -> {
                    // 这块每隔定时任务时间就会执行一次  所以 如果数据库的cron表达式如有改动 则在这里查询最新的cron表达式(这样就实现了 每次改完cron 而不用重启服务器)
                    String taskCron = taskMapper.getByKey(taskKey).getTaskCron();
                    log.info(">>>>>> 任务 [ {} ] ,实时的cron={}", scheduledTask.getTaskDesc(), taskCron);
                    CronTrigger cronTrigger = new CronTrigger(taskCron);
                    return cronTrigger.nextExecutionTime(triggerContext);
                });
        //将启动的任务放入 map
        scheduledFutureMap.put(taskKey, scheduledFuture);
    }

    /**
     * 任务是否已经启动
     */
    private Boolean isStart(String taskKey) {
        //校验是否已经启动
        if (scheduledFutureMap.containsKey(taskKey)) {
            if (!scheduledFutureMap.get(taskKey).isCancelled()) {
                return true;
            }
        }
        return false;
    }


    /**
     * 打印正在启动的任务
     */
    @Override
    public String printlnTask() {
        log.info("当前启动的任务项 size {}", scheduledFutureMap.size());
        String printlnTask = JSON.toJSONString(scheduledFutureMap);
        log.info("当前启动的任务项 {}", printlnTask);
        return printlnTask;
    }

    public void collectionInfo(SpScheduled sp){
        updateJobStatusAsRunning(sp.getId());
        try (Playwright playwright = Playwright.create()) {

            Browser browser = playwright.chromium().launch();
            BrowserContext context = browser.newContext();
            Page page = context.newPage();
            page.navigate(String.format(sp.getPageUrl(), 1));
            // 获取页数
            int pageNum = Integer.valueOf(page.querySelector(".page").innerText().split("/")[1].replace("页", "").trim());

            page.close();
            context.close();
            browser.close();

            QueryWrapper<SpScheduled> queryWrapper=new QueryWrapper();
            queryWrapper.eq("id",sp.getId());
            SpScheduled spScheduled = iSpScheduledService.getOne(queryWrapper);

            // 总页数-已采集的页数=剩余要采集的页数
            int shengyuNum;
            if(pageNum==1){
                shengyuNum=pageNum;
            }else{
                shengyuNum= pageNum-spScheduled.getPageNum();
            }
            // 计算线程数
            int threadNum = (shengyuNum-1)/sp.getPageThread()+1;

            // 线程运算数量
            final AtomicLong runCount = new AtomicLong(0);

            // 开启线程数
            for (int i = 0; i < threadNum ; i++) {
                int finalI = i;
                threadPoolTaskScheduler.execute(() -> {
                    log.info("当前任务为-->>{},被分解为-->>{}个线程,当前线程名称为{}",sp.getTaskDesc(),threadNum,Thread.currentThread().getName());
                    try (Playwright pw = Playwright.create()) {
                        Browser bw = pw.chromium().launch();
                        int stopnum;
                        //如果不是最后一页
                        if(finalI !=threadNum-1){
                            stopnum=(finalI +1)*sp.getPageThread();
                        }else{
                            stopnum=pageNum;
                        }
                        for (int k = finalI *threadNum+1; k <= stopnum; k++) {
                            spiderUtil.pageChuli(bw,sp.getColIden(),sp.getPageUrl(), k);

                            runCount.incrementAndGet();

                            spScheduled.setPageNum(spScheduled.getPageNum()+runCount.intValue());
                            iSpScheduledService.updateById(spScheduled);
                        }
                    }
                });

            }
        }
    }

    private void updateJobStatusAsRunning(String id) {
        SpScheduled spScheduled = iSpScheduledService.getById(id);
        spScheduled.setStartFlag(1);
        spScheduled.setStartTime(LocalDateTime.now());
        iSpScheduledService.updateById(spScheduled);
    }
}